package org.sean.framework.auth.token;

import cn.hutool.core.codec.Base64;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.joda.time.DateTime;
import org.sean.framework.code.StatusInfo;
import org.sean.framework.context.SpringApplicationContext;
import org.sean.framework.exception.StatusException;
import org.sean.framework.util.DigestUtil;
import org.sean.framework.util.GSONUtil;
import org.sean.framework.util.NumberUtil;
import org.sean.framework.util.StringUtil;

import java.io.Serializable;
import java.util.Optional;
import java.util.Set;

/**
 * JWT
 *
 * @author xielei
 */
public class JWT implements Serializable {
    // 时钟
    public static final transient Clock DEFAULT_CLOCK = System::currentTimeMillis;
    // 刷新策略
    public static final transient int HEADER_TYPE_REFRESH_EXP = 1;
    public static final transient int HEADER_TYPE_NO_EXP = 0;
    // 加密算法
    public static final transient String HEADER_ALGORITHM_HMACSHA256 = "HA";
    private static final long serialVersionUID = 49794470754667710L;
    // 秘钥
    private final transient String secretKey;
    private Header header;
    private Payload payload;
    private transient Clock clock;
    private String signature;

    public JWT(String secretKey) {
        this.secretKey = secretKey;

        header = new Header();

        payload = new Payload();

        setExp(7 * 24 * 3600 * 1000L);
    }

    /**
     * 创建 JWT
     *
     * @return JWT
     */
    public static JWT createJWT() {
        JWTProperty property = SpringApplicationContext.getBean(JWTProperty.class);
        if (property == null) {
            return null;
        }
        return new JWT(property.getSecretKey());
    }

    /**
     * 创建 JWT
     *
     * @param jwtValue jwt
     * @return JWT
     */
    public static JWT createJWT(String jwtValue) {
        return createJWT(null, jwtValue);
    }

    public static JWT createJWT(String secretKey, String jwtValue) {
        JWT jwt = null;
        if (StringUtil.isEmpty(secretKey)) {
            jwt = createJWT();
        } else {
            jwt = new JWT(secretKey);
        }
        if (jwt != null) {
            jwt = jwt.toJWT(jwtValue);
        }
        return jwt;
    }

    /**
     * 验证JWT
     *
     * @param jwtValue jwt
     * @return 是否合法
     */
    public static boolean validate(String jwtValue) {
        try {
            JWT jwt = createJWT();
            if (jwt != null) {
                return (jwt = jwt.toJWT(jwtValue)) != null && jwt.validate(false);
            }
        } catch (Exception e) {
            // do nothing
        }
        return false;
    }

    /**
     * 转换JWT
     *
     * @param jwtValue jwt
     * @return JWT
     */
    private JWT toJWT(String jwtValue) {
        try {
            String[] parts = jwtValue.split("\\.");
            String sign = DigestUtil.md5Hex(DigestUtil.sha256Hmac(parts[0] + parts[1], getKey()));
            if (sign.equals(parts[2])) {
                JWT result = new JWT(secretKey);
                result.header = GSONUtil.json2Obj(Base64.decodeStr(parts[0]), Header.class);
                result.payload = GSONUtil.json2Obj(Base64.decodeStr(parts[1]), Payload.class);
                if (HEADER_TYPE_REFRESH_EXP == result.header.typ) {
                    if (result.payload == null) {
                        return null;
                    }
                    if (result.payload.exp < System.currentTimeMillis()) {
                        return null;
                    }
                }
                if (result.toString().equals(jwtValue)) {
                    result.signature = parts[2];
                    return result;
                }
            }
        } catch (Exception e) {
            // do nothing
        }
        return null;
    }

    /**
     * 验证合法性
     *
     * @param throwException 是否抛异常
     * @return 是否合法
     */
    public boolean validate(boolean throwException) {
        try {
            if (payload == null) {
                throw new StatusException(StatusInfo.notLoginError());
            } else {
                // jwt 过期
                if (HEADER_TYPE_REFRESH_EXP == header.typ) {
                    long current = getClock().currentTime();
                    long exp = payload.exp;
                    if (current > exp) {
                        throw new StatusException(StatusInfo.loginTimeoutError());
                    }
                }
                // 非法jwt
                if (StringUtil.isEmpty(payload.uid)) {
                    throw new StatusException(StatusInfo.notLoginError());
                }

                long uid = NumberUtil.getLong(payload.uid, 0);

                // 非法jwt
                if (uid <= 0) {
                    throw new StatusException(StatusInfo.notLoginError());
                }
            }
        } catch (StatusException e) {
            if (throwException) {
                throw e;
            } else {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取用户ID
     *
     * @param throwException is need throw exception
     * @return Uid
     */
    public String getUid(boolean throwException) {
        if (validate(throwException)) {
            return payload.uid;
        }
        return null;
    }

    /**
     * 秘钥
     *
     * @return 秘钥
     */
    private byte[] getKey() {
        return DigestUtil.sha256Hex(secretKey.trim()).getBytes();
    }


    /**
     * 获取时钟
     *
     * @return Clock
     */
    public Clock getClock() {
        if (clock == null) {
            clock = Optional.ofNullable(SpringApplicationContext.getBean(Clock.class)).orElse(DEFAULT_CLOCK);

        }
        return clock;
    }

    @Override
    public String toString() {
        String h = Base64.encode(GSONUtil.obj2Json(this.header));
        String p = Base64.encode(GSONUtil.obj2Json(this.payload));
        if (HEADER_ALGORITHM_HMACSHA256.equals(this.header.alg)) {
            this.signature = DigestUtil.md5Hex(DigestUtil.sha256Hmac(h + p, getKey()));
        } else {
            throw new IllegalArgumentException("algorithm not support");
        }
        return h + "." + p + "." + signature;
    }

    /**
     * 设置有效期
     *
     * @param period 有效期
     */
    public void setExp(long period) {
        payload.nbf = getClock().currentTime();
        DateTime dateTime = new DateTime(payload.nbf.longValue()).plus(period);
        int hourOfDay = dateTime.hourOfDay().get();
        // 26 表示 截止凌晨2点过期,为了防止用户在24-2点之间操作jwt过期
        int delta = 26 - hourOfDay;
        payload.exp = dateTime.plusHours(delta).getMillis();
    }

    public Header getHeader() {
        return header;
    }

    public Payload getPayload() {
        return payload;
    }

    /**
     * 时钟
     */
    public interface Clock {
        /**
         * 获取当前时间
         *
         * @return 时间戳
         */
        long currentTime();
    }

    /**
     * 头信息
     */
    @Data
    public static class Header implements Serializable {
        /**
         * 类型
         */
        private Integer typ = HEADER_TYPE_NO_EXP;
        /**
         * 算法
         */
        private String alg = HEADER_ALGORITHM_HMACSHA256;

    }

    /**
     * 数据包
     */
    @Data
    @NoArgsConstructor
    public static class Payload implements Serializable {
        /**
         * jwt签发者
         */
        private String iss;
        /**
         * jwt所面向的用户(渠道/来源等语义)
         */
        private String sub;
        /**
         * jwt来源
         */
        private String src;
        /**
         * 接收jwt的一方
         */
        private String aud;
        /**
         * jwt的过期时间，这个过期时间必须要大于签发时间
         */
        private Long exp;
        /**
         * jwt的签发时间
         */
        private Long nbf;
        /**
         * 定义在什么时间之前，该jwt都是不可用的.
         */
        private Long iat;
        /**
         * jwt的唯一身份标识，主要用来作为一次性token,从而回避重复攻击。
         */
        private String jti;
        /**
         * 唯一ID
         */
        private String uid;

        /**
         * 自定义属性
         */
//        private String attr;
        /**
         * 用户身份
         */
        private Set<Integer> idf;

    }
}
